#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
// fix sdl main on Windows platform
#define SDL_MAIN_HANDLED
#include <SDL.h>
// fix glew static link problem
#define GLEW_STATIC
#include <GL/glew.h>

#define GL_GLEXT_PROTOTYPES
#include <SDL_opengl.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "editor.hpp"
#include "file_browser.hpp"
#include "linear_algo.hpp"
#include "free_glyph.hpp"
#include "simple_renderer.hpp"
#include "common.hpp"
#include "lexer.hpp"

void MessageCallback(GLenum source,
                     GLenum type,
                     GLuint id,
                     GLenum severity,
                     GLsizei length,
                     const GLchar *message,
                     const void *userParam)
{
    (void)source;
    (void)id;
    (void)length;
    (void)userParam;
    // TODO: fix this method
}

static Free_Glyph_Atlas atlas = {0};
static Simple_Renderer sr = {0};
static Editor editor = {0};
static File_Browser fb = {0};

void handle_sdl_event(bool &quit_window, bool &using_file_broser, SDL_Event &sdl_event)
{
    while (!quit_window)
    {
        // TODO: maybe this is should be define outside this method, but in main func
        // const Uint32 start = SDL_GetTicks();
        while (SDL_PollEvent(&sdl_event))
        {
            switch (sdl_event.type)
            {
            case SDL_QUIT:
            {
                quit_window = true;
            }
            break;
            case SDL_KEYDOWN:
            {
                SDL_Log("sdl keydown works\n");
            }
            break;
            } // switch end
        } // while end

#if 0 // ------------------- temp not using this code
        const Uint32 duration = SDL_GetTicks() - start;
        const Uint32 delta_time_ms = 1000 / FPS;
        if (duration < delta_time_ms) {
            SDL_Delay(delta_time_ms - duration);
        }
#endif
    }
}

/// @brief main func start up the editor program
/// @param argc
/// @param argv
/// @return 0 for sure ok, other case should be failed like return 1 -1 ...
int main(int argc, char const *argv[])
{

    // ----------------------------------------------
    Errno errcode;
    FT_Library libaray = {0}; // freetype 2 lib?
    FT_Error ft_errcode = FT_Init_FreeType(&libaray);
    if (ft_errcode)
    {
        fprintf(stderr, "ERROR: Could not init FreeType2 library\n");
        return 1;
    }

    FT_Face face;
    ft_errcode = FT_New_Face(libaray, font_file_path, 0, &face);
    if (ft_errcode == FT_Err_Unknown_File_Format)
    {
        fprintf(stderr, "ERROR: `%s` has an unknown format\n", font_file_path);
        return 1;
    }
    else if (ft_errcode)
    {
        fprintf(stderr, "ERROR: Could not load file `%s`\n", font_file_path);
        return 1;
    }

    FT_UInt pixel_size = FREE_GLYPH_FONT_SIZE;
    ft_errcode = FT_Set_Pixel_Sizes(face, 0, pixel_size);
    if (ft_errcode)
    {
        fprintf(stderr, "ERROR: Could not set pixel size to %u\n", pixel_size);
        return 1;
    }

    if (argc > 1)
    {
        const char *file_path = argv[1];
        errcode = editor_load_from_file(&editor, file_path);
        if (errcode != 0)
        {
            fprintf(stderr, "ERROR: Could not read file %s: %s\n",
                    file_path, strerror(errcode));
            return 1;
        }
    }

    const char *dir_path = ".";
    errcode = fb_open_dir(&fb, dir_path);
    if (errcode != 0)
    {
        fprintf(stderr, "ERROR: Could not read directory %s: %s\n",
                dir_path, strerror(errcode));
        return 1;
    }

    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        fprintf(stderr, "ERROR: Could not init SDL video : %s\n", SDL_GetError());
        return 1;
    }
    // ------------------------------------------- new a sdl window
    SDL_Window *window =
        SDL_CreateWindow(WINDOW_NAME, WINDOW_POS_X, WINDOW_POS_Y,
                         SCREEN_WIDTH, SCREEN_HEIGHT,
                         SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);

    if (window == NULL)
    {
        fprintf(stderr, "ERROR: could not create sdl window: %s\n", SDL_GetError());

        return 1;
    }

    // ------------------------------------------------ Set opengl version
    {
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

        int major;
        int minor;
        SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
        SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
        printf("GL version %d.%d\n", major, minor);
    }

    if (SDL_GL_CreateContext(window) == NULL)
    {
        fprintf(stderr, "ERROR: could not create opengl context : %s\n", SDL_GetError());
        return 1;
    }
    // -------------------------------- glew Init
    GLenum glewErr = glewInit();
    if (GLEW_OK != glewErr)
    {
        fprintf(stderr, "ERROR: could not int GLEW : %s\n", SDL_GetError());
        return 1;
    }

    // --------------------------------------------------- enable stuffs
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    if (GLEW_ARB_debug_output)
    {
        glEnable(GL_DEBUG_OUTPUT);
        glDebugMessageCallback(MessageCallback, 0); // use the above message callback
    }
    else
    {
        fprintf(stderr, "WARNNING: GLEW_ARB_debug_output is not available");
    }

    simple_renderer_init(&sr);
    FreeGlyphAtlasInit(&atlas, face);
    editor.atlas = &atlas;
    editor_retokenize(&editor);

    // ------------------------------------- SDL event handle things
    bool quit = false;
    bool file_browser = false;
    SDL_Event event = {0};
    const Uint32 start = SDL_GetTicks();
    handle_sdl_event(quit, file_browser, event); // ------------------ event helper
    const Uint32 duration = SDL_GetTicks() - start;
    const Uint32 delta_time_ms = 1000 / FPS;
    if (duration < delta_time_ms)
    {
        SDL_Delay(delta_time_ms - duration);
    }

    // ------------------------------- after sdl event

    // --------------------------------------------------------- main func return value
    return 0;
}
